﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CarbonExchange.AmeeQuery;
using CarbonExchange.Dal;
using CarbonExchange.Bll.Exceptions;

namespace CarbonExchange.Bll
{
    public class UserFactory : FactoryBase<User>
    {
        public UserFactory() : base() { }

        public UserFactory(IAmeeCommand ameeCommand) : base(ameeCommand) { }

        public UserFactory(Context dalContext) : base(dalContext) { }

        public UserFactory(IAmeeCommand ameeCommand, Context dalContext) : base(ameeCommand, dalContext) { }

        protected override void MarkItemForDeletion(User item)
        {
            if (item.Id > 0)
            {
                var tmp = base.DalContext.Users.Where(x => x.Id == item.Id).Single();
                if (tmp != null)
                {
                    tmp.Deleted = true;
                }
            }
        }

        protected override void PopulateDataChanges(User item, object dataItem)
        {
            var tmp = dataItem as Dal.User;
            item.Id = tmp.Id;
            item.Created = tmp.Created.ToLocalTime();
        }

        protected override object GetDataItemForUpdate(User item)
        {
            Dal.User dbItem = null;
            if (item.Id > 0)
            {
                dbItem = DalContext.Users.Where(x => x.Id == item.Id).Single();
            }
            else
            {
                dbItem = new CarbonExchange.Dal.User();
                DalContext.Users.InsertOnSubmit(dbItem);
                dbItem.SiteId = item.SiteId;
                dbItem.Created = DateTime.Now.ToUniversalTime();
            }

            dbItem.Anon = item.Anon;
            dbItem.Comment = item.Comment;
            if (dbItem.CountryId != item.CountryId)
            {
                Dal.Country tmpCountry = base.DalContext.Countries.Where(x => x.Id == item.CountryId).Single();
                dbItem.Country = tmpCountry;
            }

            dbItem.Department = item.Department;
            dbItem.EmailAddress = item.EmailAddress;
            dbItem.EmailAuthenticated = item.EmailAuthenticated;
            dbItem.FirstName = item.FirstName;
            dbItem.LastName = item.LastName;
            dbItem.Password = item.EncodedPassword;
            dbItem.Relative = item.Relative;
            dbItem.Salt = item.Salt;
            dbItem.State = item.State;
            dbItem.ThumnailId = item.ThumnailId;

            return dbItem;
        }

        protected IQueryable<Dal.User> GetSelection()
        {
            return base.DalContext.Users.Where(x => !x.Deleted).OrderBy(x => x.LastName).ThenBy(x => x.FirstName);
        }

        public int CountAllBySiteId(int siteId)
        {
            return GetSelection().Where(x => x.SiteId == siteId).Count();
        }

        public IQueryable<User> GetAllBySiteId(int siteId)
        {
            return GetSelection().Where(x => x.SiteId == siteId).Convert(base.DalContext);
        }

        public int CountAllSuperAdmins(int siteId)
        {
            return GetSelection().Where(x => x.SiteId == null).Count();
        }

        public IQueryable<User> GetAllSuperAdmins()
        {
            return GetSelection().Where(x => x.SiteId == null).Convert(base.DalContext);
        }

        public User GetById(int id)
        {
            return GetSelection().Where(x => x.Id == id).SingleOrDefault().Convert(base.DalContext);
        }

        public User GetByEmailAddress(int siteId, string emailAddress)
        {
            return GetSelection().Where(x => (x.SiteId == siteId || x.SiteId == null) && x.EmailAddress == emailAddress).SingleOrDefault().Convert(base.DalContext);
        }

        public User GetByLoginKey(Guid guid)
        {
            var tmp = from u in GetSelection()
                      join key in DalContext.UserLoginKeys on u.Id equals key.UserId
                      where key.Id == guid
                      select u;
            return tmp.FirstOrDefault().Convert(DalContext);
        }


        protected override void Validate(IQueryable<User> items, CarbonExchange.Bll.Exceptions.ValidationException exp)
        {
            items.Validate(exp, base.DalContext);
        }

        public class ActivateEmailAddressResult
        {
            public User User { get; internal set; }
            public  Results Status{ get; internal set; }
            public enum Results
            {
                CodeNotFound,
                IncorrectPassword,
                Success
            }
        }

        public ActivateEmailAddressResult ActivateEmailAddress(string activateionCode, string password)
        {
            ActivateEmailAddressResult tmpResult = new ActivateEmailAddressResult();
            var tmp = GetSelection().Join(DalContext.EmailActivationCodes, x => x.Id, y => y.UserId, (x, y) => new { User = x, ActivationCode = y }).Where(x => x.ActivationCode.Code == activateionCode).SingleOrDefault();
            if (tmp == null)
            {
                tmpResult.Status = ActivateEmailAddressResult.Results.CodeNotFound;
            }
            else
            {
                var tmpUser = tmp.User.Convert(DalContext);
                if (!tmpUser.AuthenticatePassword(password))
                {
                    tmpResult.Status = ActivateEmailAddressResult.Results.IncorrectPassword;
                }
                else
                {
                    tmp.User.EmailAddress = tmp.ActivationCode.EmailAddress;
                    tmp.User.EmailAuthenticated = true;
                    tmp.ActivationCode.Activated = true;
                    DalContext.SubmitChanges();
                    tmpResult.Status = ActivateEmailAddressResult.Results.Success;
                    tmpResult.User = tmpUser;
                }
            }
            return tmpResult;
        }

        public Guid GetLoginKey(User user)
        {
            var tmp = DalContext.UserLoginKeys.Where(x => x.UserId == user.Id).OrderByDescending(x => x.Created).FirstOrDefault();
            if (tmp == null)
            {
                tmp = new UserLoginKey();
                tmp.Id = Guid.NewGuid();
                tmp.UserId = user.Id;
                tmp.Created = DateTime.Now.ToUniversalTime();
                DalContext.UserLoginKeys.InsertOnSubmit(tmp);
                DalContext.SubmitChanges();
            }
            return tmp.Id;
        }
    }


    internal static class UserFactoryConverter
    {
        internal static IQueryable<User> Convert(this IQueryable<Dal.User> source, Dal.Context context)
        {
            return source.Select(x => ConvertItem(x, context));
        }

        internal static User Convert(this Dal.User source, Dal.Context context)
        {
            return ConvertItem(source, context);
        }

        internal static User ConvertItem(Dal.User item, Dal.Context context)
        {
            if (item != null)
            {
                return new User()
                {
                    Id = item.Id,
                    SiteId = item.SiteId,
                    EmailAddress = item.EmailAddress,
                    EncodedPassword = item.Password,
                    Salt = item.Salt,
                    FirstName = item.FirstName,
                    LastName = item.LastName,
                    Department = item.Department,
                    CountryId = item.CountryId,
                    State = item.State,
                    Comment = item.Comment,
                    ThumnailId = item.ThumnailId,
                    Anon = item.Anon,
                    Relative = item.Relative,
                    EmailAuthenticated = item.EmailAuthenticated,
                    Created = DateTime.SpecifyKind(item.Created, DateTimeKind.Utc).ToLocalTime(),
                    _Country = item.CountryLoaded ? 
                        new CarbonExchange.LinqUtilities.LazyItem<Country, CarbonExchange.Dal.Country>(item.Country.Convert()) :
                        new CarbonExchange.LinqUtilities.LazyItem<Country, CarbonExchange.Dal.Country>(context.Countries.Where(x => x.Id == item.CountryId), new CarbonExchange.LinqUtilities.LazyItem<Country, CarbonExchange.Dal.Country>.Convertion(CountryFactoryConverter.ConvertItem))
                    
                };
            }
            else
            {
                return null;
            }
        }

        internal static Dal.User Convert(this User source, Dal.Context context)
        {
            return ConvertItem(source, context);
        }

        internal static Dal.User ConvertItem(User item, Dal.Context context)
        {
            Dal.User dbItem = null;
            if (item.Id > 0)
            {
                dbItem = context.Users.Where(x => x.Id == item.Id).Single();
            }
            else
            {
                dbItem = new CarbonExchange.Dal.User();
                context.Users.InsertOnSubmit(dbItem);
                dbItem.SiteId = item.SiteId;
                dbItem.Created = DateTime.Now.ToUniversalTime();
            }

            dbItem.Anon = item.Anon;
            dbItem.Comment = item.Comment;
            if (item._Country != null && item._Country.Loaded)
            {
                dbItem.Country = item.Country.Convert(context);
            }
            else
            {
                dbItem.CountryId = item.CountryId;
            }
            dbItem.Department = item.Department;
            dbItem.EmailAddress = item.EmailAddress;
            dbItem.EmailAuthenticated = item.EmailAuthenticated;
            dbItem.FirstName = item.FirstName;
            dbItem.LastName = item.LastName;
            dbItem.Password = item.EncodedPassword;
            dbItem.Relative = item.Relative;
            dbItem.Salt = item.Salt;
            dbItem.State = item.State;
            dbItem.ThumnailId = item.ThumnailId;

            return dbItem;
        }

        internal static void Validate(this IQueryable<User> source, Exceptions.ValidationException e, Dal.Context context)
        {
            Dictionary<int, List<EmailRestriction>> emailRestrictions = new Dictionary<int, List<EmailRestriction>>();
            foreach (User item in source)
            {
                item.Validate(e, context, emailRestrictions);
            }
            var duplicates = source.GroupBy(x => x.EmailAddress).Where(x => x.Count() > 1);
            foreach (var duplicate in duplicates)
            {
                e.Add(string.Format("Email Address '{0}' specified more than one in update", duplicate.Key));
            }
        }

        internal static void Validate(this User source, Exceptions.ValidationException e, Dal.Context context)
        {
            source.Validate(e, context, new Dictionary<int, List<EmailRestriction>>());
        }

        internal static void Validate(this User source, Exceptions.ValidationException e, Dal.Context context, Dictionary<int, List<EmailRestriction>> emailRestrictions)
        {
            if (string.IsNullOrEmpty(source.EmailAddress))
            {
                e.Add("Email Address must be set.");
            }
            else if (!System.Text.RegularExpressions.Regex.Match(source.EmailAddress, User.EmailValidationExpression).Success)
            {
                e.Add(new InvalidEmailAddressValidationExceptionItem(string.Format("Email Address '{0}' is an invalid email address.", source.EmailAddress)));
            }
            else
            {
                //if siteId set then check for all users with same site id and all super users ie not siteId
                //check for duplicate email
                var duplicates = context.Users.Where(x => x.EmailAddress == source.EmailAddress).Where(x => x.Id != source.Id);
                if (source.SiteId.HasValue)
                {
                    duplicates = duplicates.Where(x => x.SiteId.Value == source.SiteId.Value || x.SiteId == null);
                }
                if (duplicates.Count() > 0)
                {
                    e.Add(new DuplicateEmailAddressValidationExceptionItem(string.Format("Email Address '{0}' is already in use.", source.EmailAddress)));
                }
            }

            if (string.IsNullOrEmpty(source.FirstName))
            {
                e.Add("First Name must be set.");
            }

            if (string.IsNullOrEmpty(source.LastName))
            {
                e.Add("Last Name must be set.");
            }

            if (source._Country != null && source._Country.Loaded)
            {
                source.Country.Validate(e, context);
            }
            else if(! (source.CountryId > 0))
            {
                e.Add("Country Id must be set.");
            }

            if (source.SiteId != null && !string.IsNullOrEmpty(source.EmailAddress))
            {
                EmailRestrictionFactory factory = new EmailRestrictionFactory(context);
                if (!emailRestrictions.ContainsKey(source.SiteId.Value))
                {
                    emailRestrictions[source.SiteId.Value] = factory.GetAllBySiteId(source.SiteId.Value).ToList();
                }
                if(!factory.ValidateEmail(source.EmailAddress, emailRestrictions[source.SiteId.Value].AsQueryable()))
                {
                    e.Add(new NotApprovedEmailAddressValidationExceptionItem(string.Format("Email Address '{0}' did not match any of the restrictions", source.EmailAddress)));
                }
            }

        }
    }
}
